#include "gwbase/base/file_ops.h"

#include <unistd.h>
#include <errno.h>
#include <gwbase/base/logging.h>
#include <gwbase/base/path.h>

namespace dicom {

WritableFile::~WritableFile() { Close(); }

Status WritableFile::Open(const std::string& path, bool truncate,
                          WritableFilePtr* file) {
  FILE* fp = NULL;
  if (truncate)
    fp = fopen(path.c_str(), "wb+");
  else
    fp = fopen(path.c_str(), "ab+");
  if (fp == NULL) return IOError("WritableFile::Open()", errno);

  file->reset(new WritableFile(fp));
  return Status();
}

Status WritableFile::Append(const void* data, size_t n) {
  const char* pos = static_cast<const char*>(data);
  size_t remain = n;
  while (remain > 0) {
    size_t n = fwrite(pos, 1, remain, f_);
    if (n == 0 && ferror(f_)) return IOError("WritableFile::Append()", errno);

    assert(n > 0);
    pos += n;
    remain -= n;
  }

  return Status();
}

Status WritableFile::Append(const Slice& data) {
  return Append(data.begin(), data.size());
}

Status WritableFile::Sync() {
  // flush before sync
  Status s = Flush();
  if (!s.ok()) return s;

  if (fdatasync(fileno(f_))) {
    return IOError("WritableFile::Sync()", errno);
  }
  return Status();
}

Status WritableFile::Flush() {
  if (fflush(f_)) {
    return IOError("WritableFile::Flush()", errno);
  }
  return Status();
}

Status WritableFile::Close() {
  if (f_ != NULL) {
    fclose(f_);
    f_ = NULL;
  }

  return Status();
}

Status WritableFile::SetMtime(const Timestamp& mtime) {
  Status s;
  struct stat st;
  if (::fstat(fileno(f_), &st) < 0) {
    return IOError("SequentialFile::SetMTime()", errno);
  }

  struct timeval times[2];
  times[0].tv_sec = st.st_atim.tv_sec;
  times[0].tv_usec = st.st_atim.tv_nsec / 1000;

  times[0].tv_sec = 0;
  times[0].tv_usec = 0;
  times[1] = mtime.ToTimeval();

  if (::futimes(fileno(f_), times) < 0) {
    return IOError("SequentialFile::SetMTime()", errno);
  }

  return s;
}

Status SequentialFile::Open(const std::string& path, SequentialFilePtr* file) {
  FILE* fp = NULL;
  fp = fopen(path.c_str(), "rb");
  if (fp == NULL) return IOError("SequentialFile::Open()", errno);

  file->reset(new SequentialFile(fp));
  return Status();
}

SequentialFile::~SequentialFile() {
  if (f_ == NULL) return;

  if (fclose(f_)) {
    LOG_ERROR << IOError("SequentialFile::Close()", errno);
  }
}

Status SequentialFile::Read(void* buf, size_t len, size_t* read_len) {
  *read_len = 0;

  size_t n = fread(buf, 1, len, f_);
  if (ferror(f_)) return IOError("SequentialFile::Read()", errno);

  *read_len = n;
  return Status();
}

Status SequentialFile::SeekTo(off_t pos) {
  if (fseek(f_, pos, SEEK_SET)) {
    return IOError("SequentialFile::SeekTo()", errno);
  }
  return Status();
}

Status ReadSmallFile(const std::string& path, std::string* content) {
  assert(content->empty());

  SequentialFilePtr file;
  Status s = SequentialFile::Open(path, &file);
  if (!s.ok()) return s;

  char buf[1024];
  size_t read = 0;
  while (true) {
    s = file->Read(buf, 1024, &read);
    if (!s.ok()) return s;

    if (read == 0) break;
    content->append(buf, read);
  }

  return Status();
}

Status WriteStringToFile(const std::string& path, const std::string& data) {
  WritableFilePtr file;
  Status s;
  s = WritableFile::Open(path, true, &file);
  if (!s.ok()) return s;

  s = file->Append(Slice(data));
  if (!s.ok()) return s;

  s = file->Sync();
  if (!s.ok()) return s;

  return s;
}

Status CreateDir(const std::string& path) {
  if (mkdir(path.c_str(), 0755)) {
    if (errno != EEXIST) return IOError("CreateDir()", errno);

    // may be not a directory
    FileInfo info;
    Status s = StatPath(path, &info);
    if (!s.ok()) return s;

    if (!info.is_dir()) return IOError("CreateDir()", EEXIST);
  }
  return Status();
}

Status CreateDirRecurisve(const std::string& path) {
  std::string parent = path::Parent(path);
  if (parent.empty() || parent == "/") return CreateDir(path);

  FileInfo info;
  Status s = StatPath(parent, &info);
  if (info.is_dir()) {
    return CreateDir(path);
  } else if (info.not_exists()) {
    s = CreateDirRecurisve(parent);
    if (!s.ok()) return s;
    return CreateDir(path);
  } else {
    return IOError("CreateDir()", EEXIST);
  }
}

Status StatPath(const std::string& path, FileInfo* out) {
  struct stat st;
  Status s;
  if (::stat(path.c_str(), &st) < 0) {
    if (errno == ENOENT) {
      out->type = FileInfo::kNotExists;
      return s;
    }
    out->type = FileInfo::kError;
    s = IOError("StatPath()", errno);
    return s;
  }

  out->size = st.st_size;
  out->mode = st.st_mode;
  out->mtime = Timestamp::FromTimespec(st.st_mtim);

  if (S_ISREG(st.st_mode)) {
    out->type = FileInfo::kRegular;
  } else if (S_ISDIR(st.st_mode)) {
    out->type = FileInfo::kDirectory;
  } else if (S_ISLNK(st.st_mode)) {
    out->type = FileInfo::kSymLink;
  } else if (S_ISBLK(st.st_mode)) {
    out->type = FileInfo::kBlock;
  } else if (S_ISCHR(st.st_mode)) {
    out->type = FileInfo::kChar;
  }

  return s;
}

Status GetFileSize(const std::string& path, size_t* size) {
  Status s;
  struct stat sbuf;
  if (stat(path.c_str(), &sbuf) != 0) {
    *size = 0;
    s = IOError(path, errno);
  } else {
    *size = sbuf.st_size;
  }
  return s;
}

Status SetFileMtime(const std::string& path, Timestamp mtime) {
  Status s;

  struct stat st;
  if (::stat(path.c_str(), &st) < 0) {
    s = IOError(path, errno);
    goto out;
  }

  struct timeval times[2];
  times[0].tv_sec = st.st_atim.tv_sec;
  times[0].tv_usec = st.st_atim.tv_nsec / 1000;
  times[1] = mtime.ToTimeval();
  if (::utimes(path.c_str(), times) < 0) {
    s = IOError(path, errno);
    goto out;
  }

out:
  return s;
}

Status DeleteFile(const std::string& path) {
  Status s;
  int ret = ::unlink(path.c_str());
  if (ret != 0) {
    return IOError("unlink", errno);
  }
  return s;
}

Status RenameFile(const std::string& src, const std::string& target) {
  Status s;
  int ret = ::rename(src.c_str(), target.c_str());
  if (ret != 0) {
    return IOError("unlink", errno);
  }
  return s;
}

Status Statfs(const std::string& path, struct statfs64* buf) {
  Status s;
  if (::statfs64(path.c_str(), buf) < 0) {
    return IOError("statfs64()", errno);
  }
  return s;
}

Status ScanDir(const std::string& path, DirentList* list) {
  Status s;
  struct dirent** namelist;
  int n;

  n = ::scandir(path.c_str(), &namelist, 0, alphasort);
  if (n < 0) {
    return IOError("scandir()", errno);
  }

  while (n--) {
    list->push_back(namelist[n]);
  }
  ::free(namelist);
  return s;
}

void FreeDirentList(DirentList* list) {
  for (size_t i = 0; i < list->size(); ++i) ::free(list->at(i));
  list->clear();
}
}
